package io.github.libkodi.basic_gateway.router;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import io.github.libkodi.basic_gateway.context.Context;
import io.github.libkodi.basic_gateway.router.annotations.Delete;
import io.github.libkodi.basic_gateway.router.annotations.Get;
import io.github.libkodi.basic_gateway.router.annotations.Post;
import io.github.libkodi.basic_gateway.router.annotations.Put;
import io.github.libkodi.basic_gateway.router.annotations.RouteRegex;
import io.github.libkodi.basic_gateway.tools.ClassUtils;
import io.github.libkodi.basic_gateway.tools.ValueUtils;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

public class RouterBuilder {
	public static Future<Router> create(Context ctx, ClassLoader loader, String packname, Router router) {
		return Future.future(promise -> {
			ClassUtils.scan(loader, packname).onSuccess(names -> {
				try {
					ArrayList<Class<?>> list = new ArrayList<Class<?>>();
					Iterator<String> iter = names.iterator();
					
					while (iter.hasNext()) {
						list.add(loader.loadClass(iter.next()));
					}
					
					create(ctx, list, router).onSuccess(promise::complete).onFailure(promise::fail);
				} catch (Exception e) {
					promise.fail(e);
				}
			}).onFailure(e -> {
				promise.fail(e);
			});
		});
	}
	
	public static Future<Router> create(Context ctx, Class<?> route, Router router) {
		ArrayList<Class<?>> list = new ArrayList<Class<?>>();
		list.add(route);
		return create(ctx, list, router);
	}
	
	public static Future<Router> create(Context ctx, List<Class<?>> list, Router router) {
		return Future.future(promise -> {
			if (list.size() < 1) {
				promise.complete(null);
			} else {
				ValueUtils.values(ctx.config(), "server.web.body.limit "
					+ "server.web.upload.enable "
					+ "server.web.upload.attributes.merge "
					+ "server.web.upload.delete-on-end "
					+ "server.web.upload.directory "
					+ "server.web.preallocate-body-buffer").onSuccess(info -> {
					
					try {
						boolean merge = info.getBoolean("server.web.upload.attributes.merge", true);
						boolean enableUpload = info.getBoolean("server.web.upload.enable", true);
						boolean deleteOnEnd = info.getBoolean("server.web.upload.delete-on-end", true);
						boolean preallocate = info.getBoolean("server.web.preallocate-body-buffer", false);
						long bodylimit = info.getLong("server.web.body.limit", -1L);
						String tempdir = info.getString("server.web.upload.directory", ctx.config().getString("java.io.tmpdir"));
						
						BodyHandler bodyHandler = BodyHandler.create();
						
						if (!merge) {
							bodyHandler.setMergeFormAttributes(false);
						}
						
						if (!enableUpload) {
							bodyHandler.setHandleFileUploads(false);
						}
						
						if (deleteOnEnd) {
							bodyHandler.setDeleteUploadedFilesOnEnd(true);
						}
						
						if (!preallocate) {
							bodyHandler.setPreallocateBodyBuffer(false);
						}
						
						bodyHandler.setBodyLimit(bodylimit);
						bodyHandler.setUploadsDirectory(tempdir);
						
						router.route().handler(bodyHandler);
						
						Iterator<Class<?>> iter = list.iterator();
						eachCreateRoute(ctx, router, iter, promise);
					} catch (Exception e) {
						promise.fail(e);
					}
				}).onFailure(promise::fail);
			}
		});
	}

	private static void eachCreateRoute(Context ctx, Router router, Iterator<Class<?>> iter, Promise<Router> promise) {
		if (iter.hasNext()) {
			Class<?> routeCls = iter.next();
			
			append(ctx, router, routeCls).onSuccess(v -> {
				eachCreateRoute(ctx, router, iter, promise);
			}).onFailure(promise::fail);
		} else {
			promise.complete(router);
		}
	}
	
	public static Future<Void> append(Context ctx, Router router, Class<?> cls) {
		return Future.future(promise -> {
			try {
				Object instance = ClassUtils.newInstance(cls);
				
				ClassUtils.injectContext(instance, ctx).onSuccess(v -> {
					try {
						Method[] methods = cls.getDeclaredMethods();
						
						for (Method method : methods) {
							String[] produces = null;
							String consumes = "";
							String[] urls = null;
							io.github.libkodi.basic_gateway.router.HttpMethod[] mds = null;
							
							if (method.isAnnotationPresent(io.github.libkodi.basic_gateway.router.annotations.Route.class)) {
								io.github.libkodi.basic_gateway.router.annotations.Route anno = method.getAnnotation(io.github.libkodi.basic_gateway.router.annotations.Route.class);
								produces = anno.produces();
								consumes = anno.consumes();
								urls = anno.value();
								mds = anno.method();
							} else if (method.isAnnotationPresent(RouteRegex.class)) {
								RouteRegex anno = method.getAnnotation(RouteRegex.class);
								produces = anno.produces();
								consumes = anno.consumes();
								urls = anno.value();
								mds = anno.method();
							} else if (method.isAnnotationPresent(Get.class)) {
								Get anno = method.getAnnotation(Get.class);
								produces = anno.produces();
								consumes = anno.consumes();
								urls = anno.value();
								mds = new io.github.libkodi.basic_gateway.router.HttpMethod[] { io.github.libkodi.basic_gateway.router.HttpMethod.GET };
							} else if (method.isAnnotationPresent(Post.class)) {
								Post anno = method.getAnnotation(Post.class);
								produces = anno.produces();
								consumes = anno.consumes();
								urls = anno.value();
								mds = new io.github.libkodi.basic_gateway.router.HttpMethod[] { io.github.libkodi.basic_gateway.router.HttpMethod.POST };
							} else if (method.isAnnotationPresent(Put.class)) {
								Put anno = method.getAnnotation(Put.class);
								produces = anno.produces();
								consumes = anno.consumes();
								urls = anno.value();
								mds = new io.github.libkodi.basic_gateway.router.HttpMethod[] { io.github.libkodi.basic_gateway.router.HttpMethod.PUT };
							} else if (method.isAnnotationPresent(Delete.class)) {
								Delete anno = method.getAnnotation(Delete.class);
								produces = anno.produces();
								consumes = anno.consumes();
								urls = anno.value();
								mds = new io.github.libkodi.basic_gateway.router.HttpMethod[] { io.github.libkodi.basic_gateway.router.HttpMethod.DELETE };
							} else {
								continue;
							}
							
							if (urls != null && mds != null && urls.length > 0) {
								if (mds.length < 1) {
									mds = new io.github.libkodi.basic_gateway.router.HttpMethod[] { io.github.libkodi.basic_gateway.router.HttpMethod.GET };
								}
								
								for (String url : urls) {
									for (io.github.libkodi.basic_gateway.router.HttpMethod md : mds) {
										createRouteHandler(ctx, router, ctx.intercepter(), instance, method, url, md, produces, consumes);
									}
								}
							}
						}
						
						promise.complete();
					} catch (Exception e) {
						promise.fail(e);
					}
				}).onFailure(promise::fail);
			} catch (Exception e) {
				promise.fail(e);
			}
		});
	}

	private static void createRouteHandler(Context ctx, Router router, RouteIntercepterImpl routeIntercepter, Object instance,
			Method runtimeMethod, String url, io.github.libkodi.basic_gateway.router.HttpMethod httpMethod, String[] produces,
			String consumes) {
		io.vertx.ext.web.Route route = router.route(url).method(HttpMethod.valueOf(httpMethod.name()));
		
		if (consumes != null && !"".equals(consumes)) {
			route.consumes(consumes);
		}
		
		if (produces != null && produces.length > 0) {
			for (String produce : produces) {
				route.produces(produce);
			}
		}
		
		route.handler(new RouteHandler(ctx, routeIntercepter, instance, runtimeMethod));
	}
}
